<?php
/**
 * 短信业务逻辑基类
 * User: 苏近之
 * Date: 2019-03-27
 * Time: 01:46
 */

namespace app\common\base;

use app\common\enum\CacheKeys;
use app\common\enum\LogCategory;
use app\common\enum\LogLevel;
use app\common\helper\spl\DateTime;
use app\common\service\LogService;
use app\user\exception\SmsThanIpLimit;
use app\user\exception\SmsThanPhoneLimit;
use app\user\exception\SmsThanTimeIntervalLimit;
use app\common\service\SmsService as CommonSmsServer;
use think\facade\Cache;
use think\facade\Request;

class SmsService
{

    protected static $phone;
    protected static $templateCode;

    /**
     * 发送注册短信前置校验
     * @param $ipLimit
     * @param $phoneLimit
     * @param \Closure|null $otherCheck
     * @throws SmsThanIpLimit
     * @throws SmsThanPhoneLimit
     * @throws SmsThanTimeIntervalLimit
     */
    protected static function before($ipLimit, $phoneLimit, \Closure $otherCheck = null)
    {
        // 检查发送时间间隔是否太短
        $timeLimitKey = sprintf(CacheKeys::SMS_SEND_INTERVAL_LIMIT, static::$templateCode, static::$phone);
        if(Cache::has($timeLimitKey)){
            throw new SmsThanTimeIntervalLimit();
        }
        // 检查相同 IP 是否超过限制
        $ipLimitKey = sprintf(CacheKeys::SMS_IP_LIMIT, static::$templateCode, static::$phone);
        if(Cache::get($ipLimitKey, 0) > $ipLimit){
            throw new SmsThanIpLimit();
        }
        // 检查相同手机号在一天内是否超过限制
        $phoneLimitKey = sprintf(CacheKeys::SMS_PHONE_LIMIT, static::$templateCode, static::$phone);
        if(Cache::get($phoneLimitKey, 0) > $phoneLimit) {
            throw new SmsThanPhoneLimit();
        }
        // 执行其他特定检查
        !is_null($otherCheck) && call_user_func($otherCheck);
    }

    /**
     * 发送短信
     * @param int $expire
     * @return mixed
     */
    protected static function send($expire = 300)
    {
        $verifyCode = mt_rand(1000, 9999);
        self::setVerifyCodeCache($verifyCode, $expire);
        $result = CommonSmsServer::send(static::$phone, $verifyCode,
            static::$templateCode);

        return $result;
    }

    /**
     * 发送注册短信后置事件
     * @param $intervalTime
     */
    protected static function after($intervalTime)
    {
        // 设置缓存
        self::setSendIntervalCache($intervalTime);
        $ipLimit = self::setIpLimitCache();
        $phoneLimit = self::setPhoneLimitCache();
        // 记录日志
        LogService::write('SEND REGISTER SMS', LogCategory::USER_REGISTER, LogLevel::NOTICE, [
            'phone' => self::$phone,
            'ipLimit' => $ipLimit,
            'phoneLimit' => $phoneLimit,
            'ip' => Request::ip()
        ]);
    }

    /**
     * 设置验证码缓存
     * @param $verifyCode
     * @param int $expire
     */
    protected static function setVerifyCodeCache($verifyCode, $expire = 60)
    {
        $key = sprintf(CacheKeys::SMS_VERIFY_CODE, static::$templateCode, static::$phone);
        Cache::set($key, $verifyCode, $expire);
    }

    /**
     * 设置发送时间间隔缓存
     * @param $intervalTime
     */
    protected static function setSendIntervalCache($intervalTime)
    {
        $sendIntervalKey = sprintf(CacheKeys::SMS_SEND_INTERVAL_LIMIT, static::$templateCode, static::$phone);
        Cache::set($sendIntervalKey,true, $intervalTime);
    }

    /**
     * 设置 IP 每日发送限制缓存
     * @return int
     */
    protected static function setIpLimitCache()
    {
        $expireToToday = DateTime::getToTomorrowTimestamp();
        $ipLimitKey = sprintf(CacheKeys::SMS_IP_LIMIT, static::$templateCode, static::$phone);
        $ipLimit = (int)Cache::get($ipLimitKey, 0);
        Cache::set($ipLimitKey, (int)Cache::get($ipLimitKey, 0) + 1, $expireToToday);

        return $ipLimit;
    }

    /**
     * 设置手机号每日发送限制缓存
     * @return int
     */
    protected static function setPhoneLimitCache()
    {
        $expireToToday = DateTime::getToTomorrowTimestamp();
        $phoneLimitKey = sprintf(CacheKeys::SMS_PHONE_LIMIT, static::$templateCode, static::$phone);
        $phoneLimit = (int)Cache::get($phoneLimitKey, 0);
        Cache::set($phoneLimitKey,  $phoneLimit + 1, $expireToToday);

        return $phoneLimit;
    }
}